package hos.map.coordinate;

import hos.map.tool.MathUtil;

/**
 * <p>Title: CoordinateConverter </p>
 * <p>Description:
 * // WGS84坐标系：即地球坐标系，国际上通用的坐标系。
 * // GCJ02坐标系：即火星坐标系，WGS84坐标系经加密后的坐标系。Google Maps，高德在用。
 * // BD09坐标系：即百度坐标系，GCJ02坐标系经加密后的坐标系。
 * </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2023-12-07 22:57
 */
public class CoordinateConverter {
    private static double getEccentricityRatio() {
        return MathUtil.getEccentricityRatio();
    }

    private static double getEarthRadius() {
        return MathUtil.getEarthRadius();
    }

    private static double getPI() {
        return MathUtil.getPI();
    }

    private static double getPIX() {
        return MathUtil.getPIX();
    }

    /**
     * 坐标转换为 百度坐标系(bd09II)
     */
    public static LatLng toBaiDu(LatLng latLng) {
        if (latLng.getType() == CoordinateType.gcj02) {
            return gcj02ToBaidu(latLng.getLatitude(), latLng.getLongitude());
        }
        if (latLng.getType() == CoordinateType.common) {
            return gcj02ToBaidu(latLng.getLatitude(), latLng.getLongitude());
        }
        if (latLng.getType() == CoordinateType.wgs84) {
            return wgs84ToBaiDu(latLng.getLatitude(), latLng.getLongitude());
        }
        return latLng;
    }

    /**
     * 坐标转换为 火星坐标系(GCJ)
     */
    public static LatLng toAMap(LatLng latLng) {
        if (latLng.getType() == CoordinateType.bd09mc) {
            return baiduToGcj02(latLng.getLatitude(), latLng.getLongitude());
        }
        if (latLng.getType() == CoordinateType.gcj02) {
            return latLng;
        }
        if (latLng.getType() == CoordinateType.wgs84) {
            return wgs84ToGcj02(latLng.getLatitude(), latLng.getLongitude());
        }
        return latLng;
    }

    /**
     * 坐标转换为 地球坐标系(WGS - 84)
     */
    public static LatLng toWgs(LatLng latLng) {
        if (latLng.getType() == CoordinateType.bd09mc) {
            return baiduToWgs84(latLng.getLatitude(), latLng.getLongitude());
        }
        if (latLng.getType() == CoordinateType.gcj02) {
            return gcj02ToWgs84(latLng.getLatitude(), latLng.getLongitude());
        }
        if (latLng.getType() == CoordinateType.common) {
            return gcj02ToWgs84(latLng.getLatitude(), latLng.getLongitude());
        }
        return latLng;
    }

    /**
     * 百度坐标(bd09II)转火星坐标(GCJ02)
     * 百度——>谷歌、高德
     *
     * @param lat 百度坐标纬度
     * @param lon 百度坐标经度
     */
    public static LatLng baiduToGcj02(double lat, double lon) {
        double X_PI = getPIX();
        double x = lon - 0.0065;
        double y = lat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
        double gg_lng = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        return new LatLng(gg_lat, gg_lng, CoordinateType.gcj02);
    }


    /**
     * 火星坐标系(GCJ02)转百度坐标系(bd09II)
     * 谷歌、高德——>百度
     *
     * @param gcjLat 火星坐标纬度
     * @param gcjLon 火星坐标经度
     */
    public static LatLng gcj02ToBaidu(double gcjLat, double gcjLon) {
        double X_PI = getPIX();
        double z = Math.sqrt(gcjLon * gcjLon + gcjLat * gcjLat) + 0.00002 * Math.sin(gcjLat * X_PI);
        double theta = Math.atan2(gcjLat, gcjLon) + 0.000003 * Math.cos(gcjLon * X_PI);
        double bd_lng = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new LatLng(bd_lat, bd_lng, CoordinateType.bd09mc);
    }

    /**
     * 火星坐标系(GCJ02)转地球坐标系(WGS84)
     *
     * @param gcjLat 火星坐标纬度
     * @param gcjLon 火星坐标经度
     */
    public static LatLng gcj02ToWgs84(double gcjLat, double gcjLon) {
        LatLng map = transform(gcjLat, gcjLon);
        return new LatLng(gcjLat * 2 - map.getLatitude(),
                gcjLon * 2 - map.getLongitude(),
                CoordinateType.wgs84);
    }

    /**
     * 地球坐标系(WGS - 84)转火星坐标系(GCJ)
     *
     * @param wgLat 地球坐标纬度
     * @param wgLon 地球坐标经度
     */
    public static LatLng wgs84ToGcj02(double wgLat, double wgLon) {
        // 如果是国外坐标点，则直接返回传进来的坐标点
        if (outOfChina(wgLon, wgLat)) {
            return new LatLng(wgLat, wgLon, CoordinateType.wgs84);
        }
        return transform(wgLat, wgLon);
    }

    public static LatLng wgs84ToBaiDu(double lat, double lon) {
        LatLng map = wgs84ToGcj02(lat, lon);
        return gcj02ToBaidu(map.getLatitude(), map.getLongitude());
    }

    public static LatLng baiduToWgs84(double lat, double lon) {
        LatLng map = baiduToGcj02(lat, lon);
        return gcj02ToWgs84(map.getLatitude(), map.getLongitude());
    }

    /**
     * 地球坐标系(WGS - 84)转火星坐标系(GCJ)
     *
     * @param lat 地球坐标纬度
     * @param lon 地球坐标经度
     */
    private static LatLng transform(double lat, double lon) {
        double pi = getPI();
        double eccentricity_ratio = getEccentricityRatio();
        double earthRadius = getEarthRadius();
        double dLat = transformLat(lon - 105.0, lat - 35.0, pi);
        double dLon = transformLon(lon - 105.0, lat - 35.0, pi);
        double radLat = lat / 180.0 * pi;
        double magic = Math.sin(radLat);
        magic = 1 - eccentricity_ratio * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((earthRadius * (1 - eccentricity_ratio)) / (magic * sqrtMagic) * pi);
        dLon = (dLon * 180.0) / (earthRadius / sqrtMagic * Math.cos(radLat) * pi);
        return new LatLng(lat + dLat, lon + dLon, CoordinateType.gcj02);
    }


    /*判断是否在国内，不在国内则不做偏移*/
    private static boolean outOfChina(double lon, double lat) {
        return !(lon > 72.004 && lon < 135.05 && lat > 3.86 && lat < 53.55);
    }

    private static double transformLat(double x, double y, double pi) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    private static double transformLon(double x, double y, double pi) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
        return ret;
    }
}
